/**************************************************************************
  NetZen :  Network Sensitivity Analysis and Visualization
                             -------------------
    copyright            : (C) 2010 by Carlos D. Correa
    email                : correac@cs.ucdavis.edu
 
	Monopoly(R) is a registered trademark of Hasbro, Inc.
***************************************************************************/

/***************************************************************************
 *                                                                         *
 * Copyright: All rights reserved.  May not be used, modified, or copied   *
 * without permission.                                                     *
 *                                                                         *
 ***************************************************************************/
#include "glheaders.h"

#include "monopoly.h"

#include <assert.h>
#include <string.h>
#include <math.h>

#include "renderUtils.h"
#include "mouseUtils.h"
#include "graph.h"
#include "layout.h"
#include "controller.h"

// Create a layout class to render a specific layout that mimics the positions of the monopoly board.
class MonopolyLayout: public Layout {
public:
  vector<float> angles;
  MonopolyLayout(): Layout("MonopolyLayout", "MonopolyLayout") {
    vnodes.clear();

    // Set up positions and orientations of all 40 positions in the Monopoly board.
    // "In Jail" is considered an extra position (41st position)
    for(int i=0;i<41;i++) {
      float angle = -floor(i/10)/2.0*180.0;
      float x = 5.0 - (float) (i%10);
      float y = -5.0;
      
      if(i==0) {
	angle = -270.0;
	x = -5.0 + 0.25;
      }
      if(i==10) {
	angle = -90.0;
	x = 5.0 - 0.25;
      }
      if(i==20) {
	angle = -90.0;
	x = -5.0 + 0.25;
      }
      if(i==30) {
	angle = -270.0;
	x = 5.0 - 0.25;
      }
      if(i==40) {
	angle = -90.0;
	x =  5.5 - 0.25;
	y = -5.0;
      }
      angles.push_back(angle);
      NodeInfo n;
      n.x = x;
      n.y = y;
      vnodes.push_back(n);
    }
  }

  // Return location in absolute coordinates (after applying the rotation)
  void getAbsoluteLocation(int i, float &x, float &y, float offsetx=0.0, float offsety=0.0) {
    float angle1 = getAngle(i);
    float x1, y1;
    getLocation(i, x1, y1);
    float m[16];
    glPushMatrix();
    glLoadIdentity();
    glRotatef(angle1, 0,0,1);
    glTranslatef(x1 + offsetx,y1 + offsety,0);
    glGetFloatv(GL_MODELVIEW_MATRIX, m);
    x = m[12]; 
    y = m[13];
    glPopMatrix();
  }
  float getAngle(int i) {
    return angles[i];
  }
};

// Creates a view for the Monopoly Visualization
class MonopolyView: public View {
public:
  float zoomx, zoomy;
  float panx, pany;
  ColorMap *colors;
  bool bSetup;
  MonopolyLayout *layout;

  virtual inline float xpixels(float pixels) {
    return View::xpixels(pixels)/zoomx;
  }
  virtual inline float ypixels(float pixels) {
    return View::ypixels(pixels)/zoomx;
  }

  // Constructor. In charge of setting up properties that will appear in the UI under the inspector tab.
  MonopolyView(const char *title):View("MonopolyView", title) {
    zoomx = 1/15.0;
    panx = 0.0;
    pany = 0.0;
    setPropertyInt("Selected Square", 0);
    setPropertyFloat("Edge.Opacity",0.5);
    setPropertyStringList("Selected Direction", "Outgoing", "edgeDirections");
    setPropertyStringList("Size", "Default", "node");
    setPropertyStringList("Color", "Default", "node");
    setPropertyStringList("Label", "Default", "node");
    setPropertyStringList("Edge.Label", "Default", "edge");
    setPropertyStringList("Colormap", "Default", "colormap");
	  setPropertyStringList("Edge color", "Default", "edge");
	  setPropertyStringList("Edge colormap", "Default", "colormap");
	  setPropertyStringList("Edge opacity", "Default", "edge");
    setPropertyInt("Font Size", 10);
    setPropertyStringList("Font", "Helvetica","font");
    setPropertyString("Title", "Default");
    setPropertyInt("Title Size", 20);

    // Add monopoly colors
    colors = new ColorMap(ColorMap::DISCRETE);
    colors->insert(0,0.875990,1.000000,0.823240);
    colors->insert(1,0.319432,0.135851,0.406592);
    colors->insert(2,0.192309,0.192309,0.192309);
    colors->insert(3,0.897597,0.795178,1.000000);
    colors->insert(4,1.000000,0.300649,0.541619);
    colors->insert(5,1.000000,0.666941,0.068742);
    colors->insert(6,1.000000,0.070619,0.023438);
    colors->insert(7,1.000000,0.891249,0.029633);
    colors->insert(8,0.110582,0.587915,0.225010);
    colors->insert(9,0.051225,0.086687,1.000000);    

    for(int i=0;i<10;i++) {
      float rgb[3];
      colors->getColor(i, rgb);
      for(int k=0;k<3;k++) rgb[k] = rgb[k]*0.25 + 0.8*0.75;
      colors->setColor(i, rgb[0], rgb[1], rgb[2]);
    }

    // Create layout
    layout = new MonopolyLayout();
  }

  // Called when the controller creates the view.
  // Here, I compute derived properties such as expected income and return of investment, based
  // on other properties such as cost and price.
  virtual void setGraph(Graph *g) {
    assert(controller);
    View::setGraph(g);
    for(unsigned int i=0;i<g->nodes.size();i++) {
      float rent = g->nodes[i]->getPropertyFloat("4-Houses");
      float housescost = g->nodes[i]->getPropertyFloat("Houses-Cost");
      float price = g->nodes[i]->getPropertyFloat("Price") + 4*housescost;
      float prob = g->nodes[i]->getPropertyFloat("EigenCentrality");
      float income = rent*prob;
      float ROI = rent*prob/price;
      g->nodes[i]->setPropertyFloat("Expected Income", income);
      g->nodes[i]->setPropertyFloat("ROI", ROI);
    }
    controller->registerProperty("node", "ROI");
    g->computeNodeMinMaxFloat("ROI");
    controller->registerProperty("node", "Expected Income");
    g->computeNodeMinMaxFloat("Expected Income");

    controller->registerProperty("edgeDirections", "Outgoing");
    controller->registerProperty("edgeDirections", "Incoming");

  }

  // In charge of drawing one node, corresponding to a Monopoly square.
  void drawNode(Node *node, int i) {
    float rgbProperty[3];

    string sizeProperty = getPropertyString("Size");
    string colormap = getPropertyString("Colormap");
    string color = node->getPropertyString("Color");
    colors->getColor(controller->stringToInt("Color",color), rgbProperty);

    glPushMatrix();
    {
      float angle, x, y;
      angle = layout->getAngle(i);
      layout->getLocation(i, x, y);
      
      glRotatef(angle, 0, 0, 1);
      glTranslatef(x,y,0);

      float xt = 0.0;
      float wt = (i%10==0)? 0.25:0.5;
      float ht = 1.0;
      float wdata = 0.15;
      
      glColor4f(rgbProperty[0], rgbProperty[1], rgbProperty[2], 1.0);
      glRectf(xt -wt ,0 ,  xt + wt, 0+ ht*0.5);
      
      float rgb[3];
      getAsColor(node, "Color", colormap, rgb);
      float size = graph->getAsFloat(node, "Size", 0.0, true);

      glColor4f(rgb[0], rgb[1], rgb[2], 0.8);
      glRectf(xt-wdata, 0 ,  xt + wdata, 0 - size);
      
      glLineWidth(1);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glColor4f(0,0,0,0.15);
      glRectf(xt-wt, 0, xt + wt, 0+ ht*0.5);
            
      glColor4f(0,0,0,0.75);
      glRectf(xt -wdata, 0 ,  xt + wdata, 0 - size);
            
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }
    glPopMatrix();
  }

  // Draws the name of the square
  void drawName(Node *node, int i) {
    glColor4f(0,0,0,0.75);
    float angle, x, y;
    angle = layout->getAngle(i);
    layout->getLocation(i, x, y);

    glPushMatrix();
    {
      glRotatef(angle, 0, 0, 1);
      glTranslatef(x,y,0);
      float xt = 0.0;
      
      string labelProperty = getPropertyString("Label");
      string font = getPropertyString("Font");
      int fontsize = getPropertyInt("Font Size", 1);
      string text = node->getAsString(labelProperty);

      if(text!="") {
				
				float textw, texth;
				textBounds(textw, texth, (char*) text.c_str(), font.c_str(), fontsize);
				float ytext =
					(i==0)? -1.0: 
					(i<10)? -1.0 - texth*(float) (1 + (i+1)%3):
					(i<=20)? -1.0 - textw:
					(i<30)? -1.0 - texth*(float) ((i+1)%3):
					(i<40)? -1.0:
					-1.0 - textw;
				float xtext = 
					(i>0 && i<10)? -textw/2.0:
					(i>20 && i<30)? textw/2.0:
					0.0;
				
				drawText(xt + xtext, ytext, text.c_str(), font.c_str(), fontsize);
      }
    }
    glPopMatrix();
  }

  // Draws the label corresponding to an edge.
  void drawEdgeLabel(Edge *edge, int i, int j) {
    float weight = (getPropertyString("Selected Direction")=="Outgoing")? 0.0:1.0;
    float absx1, absy1;
    float absx2, absy2;
    layout->getAbsoluteLocation(j, absx1, absy1, 0.0, 0.5);
    layout->getAbsoluteLocation(i, absx2, absy2, 0.0, 0.5);
    int edge1 = min(i,j);
    int edge2 = max(i,j);
    int diff1 = edge2-edge1;
    int diff2 = edge1+40-edge2;

    float absxm, absym;
    float offset = min(diff1, diff2);

    float R = 4.5 - 0.2*offset;
    float theta =  (float) -j/40.0*2.0*M_PI - M_PI/4.0; //atan2(absy1, absx2);
    absxm = cos(theta)*R;
    absym = sin(theta)*R;


    string edgeProperty = getPropertyString("Edge.Label");
    string text = edge->getAsString(edgeProperty);

    if(text!="") {
      float textx, texty, textw, texth;
      textx = (1-weight)*absx1+weight*absx2;// (1-weight)*(1-weight)*absx1 + 2*weight*(1-weight)*absxm + weight*weight*absx2;
      texty = (1-weight)*absy1+weight*absy2;//(1-weight)*(1-weight)*absy1 + 2*weight*(1-weight)*absym + weight*weight*absy2;
      textBounds(textw, texth, text.c_str());
      glColor4f(1,1,1,0.5);
      glRectf(textx-textw/2,texty,textx+textw/2,texty+texth);
      glColor4f(0,0,0,0.8);
      drawText(textx-textw/2, texty, text.c_str());
    }
  }

  // Draws an edge, corresponding to a possible move in the game
  void drawEdge(Edge *edge, int i, int j) {
    float transparency = getPropertyFloat("Edge.Opacity")*graph->getAsFloat(edge, PROPERTY(getPropertyString("Edge Opacity")));
    int selectedEnd = (getPropertyString("Selected Direction")=="Outgoing")? i:j;
    if(selectedEnd!=getPropertyInt("Selected Square")) transparency=0.01*transparency;

    float absx1, absy1;
    float absx2, absy2;
    layout->getAbsoluteLocation(i, absx1, absy1, 0.0, 0.5);
    layout->getAbsoluteLocation(j, absx2, absy2, 0.0, 0.5);

    int edge1 = min(i,j);
    int edge2 = max(i,j);
    int diff1 = edge2-edge1;
    int diff2 = edge1+40-edge2;

    float absxm, absym;
    float offset = min(diff1, diff2);;

    float R = 4.5 - 0.2*offset;
    float theta =  (float) -j/40.0*2.0*M_PI - M_PI/4.0; //atan2(absy1, absx2);
    absxm = cos(theta)*R;
    absym = sin(theta)*R;


    // Draw Curve
    float rgb[3];
	  string cmap = PROPERTY(getPropertyString("Edge colormap"));
	  getAsColor(edge, PROPERTY(getPropertyString("Edge color")), cmap, rgb);
    glColor4f(rgb[0],rgb[1],rgb[2],transparency);

    glBegin(GL_LINE_STRIP); 
    {
      for(float t = 0.0;t<1.0;t+=0.1) {
	float x = (1-t)*(1-t)*absx1 + 2*(1-t)*t*absxm + t*t*absx2;
	float y = (1-t)*(1-t)*absy1 + 2*(1-t)*t*absym + t*t*absy2;
	glVertex2f(x,y);
      }
      glVertex2f(absx2,absy2);
    }
    glEnd();
  }

  // Main method for rendering the view
  virtual void render() {
    assert(graph);
    assert(graph->getStatManager());
    zoomy = zoomx*w/h;

    glPushMatrix();
    glTranslatef(0.5, 0.5, 0);
    glScalef(zoomx,zoomy,1.0);
    glTranslatef(panx, pany, 0);
    glTranslatef(-0.5, -0.5, 0);
    
    glColor4f(0,0,0,0.25);
    glLineWidth(1);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glRectf(-5.0 - 1.0, -5.0-1.0, 5.0+1.0, 5.0 + 1.0); 
    glEnable(GL_LINE_STIPPLE);
    glLineStipple(1, 0xF0F0);
    for(float level = 0.25; level<=0.75; level+=0.25) {
      glRectf(-5.0 - level, -5.0-level, 5.0+level, 5.0 + level); 
    }
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_LINE_STIPPLE);

    glColor4f(1.0, 1.0, 1.0, 1.0);
    glRectf(-4.5,-4.5,4.5,4.5);

    string font = getPropertyString("Font");
    string title = getPropertyString("Title");
    int titleSize = getPropertyInt("Title Size", 1);
    glColor4f(0,0,0,0.9);
    float textw, texth;
    textBounds(textw, texth, title.c_str(), font.c_str(), titleSize);
    drawText(-textw/2, 0, title.c_str(), font.c_str(), titleSize);

	
	  ElementIterator *it = graph->createNodeIterator();
	  int i = 0;
	  for(it->begin();it->hasMoreElements();) {
		  Node *node = (Node*) it->next();
		  if(node->isVisible()) {
			  drawNode(node,i);
		  }
		  i++;
	  }
	  graph->destroyIterator(it);

	  it = graph->createNodeIterator(); i = 0;
	  for(it->begin();it->hasMoreElements();) {
		  Node *node = (Node*) it->next();
		  if(node->isVisible()) {
			  drawName(node,i);
			  i++;
		  }
	  }
	  graph->destroyIterator(it);
	  
	  ElementIterator *it2 = graph->createEdgeIterator();
	  for(it2->begin();it2->hasMoreElements();) {
		  Edge *edge = (Edge*) it2->next();
		  drawEdge(edge,edge->getNode0()->getIndex(), edge->getNode1()->getIndex());
      }
	  graph->destroyIterator(it2);
	  it2 = graph->createEdgeIterator();
	  for(it2->begin();it2->hasMoreElements();) {
		  Edge *edge = (Edge*) it2->next();
		  int i = edge->getNode0()->getIndex();
		  int j = edge->getNode1()->getIndex();
		  int selectedEnd = (getPropertyString("Selected Direction")=="Outgoing")? i:j;
		  if(selectedEnd==getPropertyInt("Selected Square")) {
			  drawEdgeLabel(edge,i,j);
		  }
	  }
	  graph->destroyIterator(it2);
    
	  
	  glPopMatrix();
  }

  virtual void click(float x, float y, int modifiers) {
    View::click(x, y, modifiers);
  }

  virtual void release(float px, float py, int modifiers) {
    View::release(px,py,modifiers);
  }

  // Zoom/Pan using mouse dragging behavior
  virtual void drag(float px, float py, float dx, float dy, int modifiers) {
    float rdx, rdy;
    toRelativeDisplacement(dx,dy,rdx,rdy);
    if(modifiers & NZ_ACTIVE_SHIFT) {
      panx += rdx/zoomx;
      pany += rdy/zoomy;
    } else if(modifiers & NZ_ACTIVE_CTRL) {
      float d = 5.0*rdy;
      zoomx *=exp(d);   
      zoomy = zoomx*w/h;
    } else {
      View::drag(px, py, dx, dy, modifiers);
    }
  }
};


// This is the view creator function, which is added to the controller. In this way,
// users can create as many Monopoly views as they want.

View *createMonopolyView(string name) {
  MonopolyView* mainView = new MonopolyView(name.c_str());
  mainView->setLocation(0.0,0.0);
  mainView->setSize(1.0,1.0);
  mainView->setVisible(true);
  mainView->addTransparentButton();
  mainView->addCloseButton();
  return mainView;
}

